package cz.muni.fi.xvano1.trinity.security;

import cz.muni.fi.xvano1.trinity.supplementary.DataSupplier;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This class acts as a security measure when user sends request to system.
 *
 * @author Jan Vano
 */
public class LoginCheck implements Filter {

    // The filter configuration object we are associated with.  If
    // this value is null, this filter instance is not currently
    // configured. 
    private FilterConfig filterConfig = null;

    public LoginCheck() {
    }

    /**
     * When request arrives it is checked wheter user is logged, if he has not
     * been logged out, or if he is not trying to log from two or more different
     * sessions concurrently. If user is logging into the session it is checked
     * wheter cookie that marked his former presence is present in the browser.
     * If it is present, he is informed that his session has expired. If not,
     * his request is passed on. Second layer of control is queried agains
     * internal table of session access where information about last time
     * accession is stored. If youser has been logged from another session and
     * that session has not expired, users request is blocked and user is
     * informed that his account is locked from other session.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     * @param chain The filter chain we are processing
     *
     * @exception IOException if an input/output error occurs
     * @exception ServletException if a servlet error occurs
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;
            String remoteUser = req.getRemoteUser();
            if (remoteUser != null) {
                if (remoteUser.contains("@META")) {
                    remoteUser = remoteUser.split("@META")[0];
                }
            }
            request.setAttribute("loggedUser", remoteUser);
            //System.out.println("Is request secure? " + req.isSecure());
            //System.out.println("User is...." + req.getRemoteUser());
            String base = request.getScheme()
                    + "://" + request.getServerName()
                    + ":" + request.getServerPort()
                    + req.getContextPath();
            String relogUrl = base
                    + "/" + "Login";
            String indexUrl = base
                    + "/" + "Index";
            String lockedAccount = base
                    + "/" + "Locked";
            //System.out.println(req.getRequestURL());
            //System.out.println(req.getServletPath());
            if (req.getRequestURL().toString().contains(("/Locked"))) {
                chain.doFilter(request, response);
                return;
            }
            if ((req.getSession().getAttribute("user") == null)) {
                boolean wasLoggedBefore = false;
                if (req.getCookies() != null) {
                    for (Cookie cookie : req.getCookies()) {
                        if ("logged".equals(cookie.getName()) && "am".equals(cookie.getValue())) {
                            wasLoggedBefore = true;
                        }
                    }
                }
                boolean wantsToRelog = false;
                if (req.getServletPath().equals("/Index")
                        && "relog".equals(req.getParameter("action"))) {
                    wantsToRelog = true;
                }
                if (wantsToRelog) {
                    chain.doFilter(request, response);
                    return;
                }
                if (wasLoggedBefore) {
                    if (req.getRequestURL().toString().contains("/Login")) {
                        chain.doFilter(request, response);
                        return;
                    } else {
                        res.sendRedirect(relogUrl);
                        return;
                    }
                } else {
                    if (req.getServletPath().equals("/Index")) {
                        chain.doFilter(request, response);
                        return;
                    } else {
                        res.sendRedirect(indexUrl);
                        return;
                    }
                }
            } else {
                Map<String, String[]> userAccess = (Map<String, String[]>) request.getServletContext().getAttribute("userAccess");
                String user = (String) req.getSession().getAttribute("user");
                if (userAccess != null) {
                    Object sync = req.getServletContext().getAttribute("userAccessSync");
                    synchronized (sync) {
                        long actualTime = Calendar.getInstance().getTimeInMillis();
                        String actualSessionId = req.getSession().getId();
                        String[] userInformation = userAccess.get(user);
                        if (userInformation != null) {
                            Long lastAccessTime = Long.parseLong(userInformation[0]);
                            String sessionId = userInformation[1];
                            if ((!actualSessionId.equals(sessionId))
                                    && ((actualTime - lastAccessTime - 10000) < (req.getSession().getMaxInactiveInterval() * 1000))) {
                                res.sendRedirect(lockedAccount);
                                return;
                            } else {
                                userAccess.put(user, new String[]{String.valueOf(actualTime), actualSessionId});
                                request.getServletContext().setAttribute("userAccess", userAccess);
                                setAdmin(req);
                                chain.doFilter(request, response);
                                return;
                            }
                        } else {
                            userAccess.put(user, new String[]{String.valueOf(actualTime), actualSessionId});
                            request.getServletContext().setAttribute("userAccess", userAccess);
                            setAdmin(req);
                            chain.doFilter(request, response);
                            return;
                        }
                    }
                } else {
                    OutputStream out = response.getOutputStream();
                    PrintWriter errorWriter = new PrintWriter(out);
                    errorWriter.println("System error. Please contact administrator and pass him this code: USRINFMISS");
                    return;
                }

            }
        }
        Throwable problem = null;
        try {
            chain.doFilter(request, response);
        } catch (Throwable t) {
            // If an exception is thrown somewhere down the filter chain,
            // we still want to execute our after processing, and then
            // rethrow the problem after that.
            problem = t;
//            t.printStackTrace(System.err);
        }

        // If there was a problem, we want to rethrow it if it is
        // a known type, otherwise log it.
        if (problem != null) {
            if (problem instanceof ServletException) {
                throw (ServletException) problem;
            }
            if (problem instanceof IOException) {
                throw (IOException) problem;
            }
            sendProcessingError(problem, response);
        }
    }

    /**
     * Return the filter configuration object for this filter.
     */
    public FilterConfig getFilterConfig() {
        return (this.filterConfig);
    }

    /**
     * Set the filter configuration object for this filter.
     *
     * @param filterConfig The filter configuration object
     */
    public void setFilterConfig(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
    }

    /**
     * Destroy method for this filter.
     */
    @Override
    public void destroy() {
        filterConfig = null;
    }

    /**
     * Init method for this filter.
     */
    @Override
    public void init(FilterConfig filterConfig) {
        this.filterConfig = filterConfig;
        if (filterConfig != null) {
        }
    }

    /**
     * Return a String representation of this object.
     */
    @Override
    public String toString() {
        if (filterConfig == null) {
            return ("loginCheck()");
        }
        StringBuilder sb = new StringBuilder("loginCheck(");
        sb.append(filterConfig);
        sb.append(")");
        return (sb.toString());
    }

    private void sendProcessingError(Throwable t, ServletResponse response) {
        String stackTrace = getStackTrace(t);

        if (stackTrace != null && !stackTrace.equals("")) {
            try {
                response.setContentType("text/html");
                PrintStream ps = new PrintStream(response.getOutputStream());
                PrintWriter pw = new PrintWriter(ps);
                pw.print("<html>\n<head>\n<title>Error</title>\n</head>\n<body>\n"); //NOI18N

                // PENDING! Localize this for next official release
                pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");
                pw.print(stackTrace);
                pw.print("</pre></body>\n</html>"); //NOI18N
                pw.close();
                ps.close();
                response.getOutputStream().close();
            } catch (Exception ex) {
            }
        } else {
            try {
                PrintStream ps = new PrintStream(response.getOutputStream());
                t.printStackTrace(ps);
                ps.close();
                response.getOutputStream().close();
            } catch (Exception ex) {
            }
        }
    }

    /**
     * Print stacktrace of a throwable object.
     *
     * @param t Throwable object to be print out
     */
    public static String getStackTrace(Throwable t) {
        String stackTrace = null;
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            pw.close();
            sw.close();
            stackTrace = sw.getBuffer().toString();
        } catch (Exception ex) {
        }
        return stackTrace;
    }

    private void setAdmin(HttpServletRequest request) {
        if (request.getSession().getAttribute("admin") == null) {
            List<String> admins = DataSupplier.getAdmins();
            if (admins.contains(request.getSession().getAttribute("user").toString())) {
                request.getSession().setAttribute("admin", true);
            }
        }
    }
}
